package com.vivo.ic.dm;

import android.content.Context;
import android.net.Uri;
import android.os.SystemClock;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;

import com.vivo.ic.VLog;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 文件名生成规则：</br>
 * </br>
 * 1.文件夹：首先由Downloads.Column.FILE_NAME_HINT字段决定，</br>
 * 其次由{@link Config#getDefaultDownloadPath()}决定，</br>
 * 最终的默认值为{@link Constants.DEFAULT_DL_PARENT} </br>
 * </br>
 * 2.文件名：由Downloads.Column.FILE_NAME_HINT字段决定</br>
 * 默认值为{@link Constants.DEFAULT_DL_FILENAME}</br>
 */
class Helpers {
    private static final String TAG = Constants.PRE_TAG + "Helpers";
    
    /** Regex used to parse content-disposition headers */
    private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern
            .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
    
    private static final Object sUniqueLock = new Object();
    
    public static Random sRandom = new Random(SystemClock.uptimeMillis());

    private static final String ONE_DOT = ".";
    private static final String TWO_DOT = "..";
    
    static String generateSaveFile(Context context, String url, String hint, String contentDisposition,
            String contentLocation, String mimeType) throws IOException {
        // 设置parent路径
        File parent = chooseParent(hint);
        
        // 设置文件名
        String name = chooseFilename(url, hint, contentDisposition, contentLocation);
        
        // 设置文件后缀
        final String prefix;
        final String suffix;
        final int dotIndex = name.lastIndexOf('.');
        final boolean missingExtension = dotIndex < 0;
        if (missingExtension) {
            prefix = name;
            suffix = chooseExtensionFromMimeType(mimeType, true);
        } else {
            prefix = name.substring(0, dotIndex);
            suffix = chooseExtensionFromFilename(mimeType, name, dotIndex);
        }
        
        // 生成最终完整的文件名
        synchronized (sUniqueLock) {
            name = generateAvailableFilenameLocked(parent, prefix, suffix);
            
            // Claim this filename inside lock to prevent other threads from
            // clobbering us. We're not paranoid enough to use O_EXCL.
            final File file = new File(parent, name);
            // file.createNewFile();
            return file.getAbsolutePath();
        }
    }
    
    private static File chooseParent(String hint) throws IOException {
        File parent = null;
        
        // 1.从hint中取parent
        if (!TextUtils.isEmpty(hint)) {
            File file = new File(Uri.parse(hint).getPath());
            parent = file.getParentFile();
            if (parent != null) {
                parent = parent.getAbsoluteFile();
            }
        }
        
        // 2.使用默认的parent
        if (parent == null) {
            String path = DownloadManager.getConfig().getDefaultDownloadPath();
            if (TextUtils.isEmpty(path)) {
                path = Constants.DEFAULT_DL_PARENT;
            }
            parent = new File(Uri.parse(path).getPath());
        }
        
        if (parent == null || !(parent.isDirectory() || parent.mkdirs())) {
            throw new IOException("Failed to create parent for " + parent);
        }
        
        return parent;
    }
    
    private static String chooseFilename(String url, String hint, String contentDisposition, String contentLocation) {
        String filename = null;
        
        // 1.如果应用定义了Downloads.Column.FILE_NAME_HINT，则优先使用数据库此字段
        if (TextUtils.isEmpty(filename) && hint != null && !hint.endsWith("/")) {
            VLog.v(TAG, "getting filename from hint");
            int index = hint.lastIndexOf('/') + 1;
            if (index > 0) {
                filename = hint.substring(index);
            } else {
                filename = hint;
            }
        }
        
        // 2.使用网站推荐的文件名，content-disposition
        if (TextUtils.isEmpty(filename) && contentDisposition != null) {
            filename = parseContentDisposition(contentDisposition);
            if (filename != null) {
                VLog.v(TAG, "getting filename from content-disposition");
                int index = filename.lastIndexOf('/') + 1;
                if (index > 0) {
                    filename = filename.substring(index);
                }
            }
        }
        
        // 3.使用网站的备选网址解析文件名
        if (TextUtils.isEmpty(filename) && contentLocation != null) {
            String decodedContentLocation = Uri.decode(contentLocation);
            if (decodedContentLocation != null && !decodedContentLocation.endsWith("/")
                    && decodedContentLocation.indexOf('?') < 0) {
                VLog.v(TAG, "getting filename from content-location");
                int index = decodedContentLocation.lastIndexOf('/') + 1;
                if (index > 0) {
                    filename = decodedContentLocation.substring(index);
                } else {
                    filename = decodedContentLocation;
                }
            }
        }
        
        // 4.使用下载的url解析文件名
        if (TextUtils.isEmpty(filename)) {
            String decodedUrl = Uri.decode(url);
            if (decodedUrl != null && !decodedUrl.endsWith("/") && decodedUrl.indexOf('?') < 0) {
                int index = decodedUrl.lastIndexOf('/') + 1;
                if (index > 0) {
                    VLog.v(TAG, "getting filename from uri");
                    filename = decodedUrl.substring(index);
                }
            }
        }
        
        // 5.全都匹配失败，则使用默认文件名
        if (TextUtils.isEmpty(filename)) {
            VLog.v(TAG, "using default filename");
            filename = Constants.DEFAULT_DL_FILENAME;
        }
        
        // The VFAT file system is assumed as target for downloads.
        // Replace invalid characters according to the specifications of VFAT.
        filename = buildValidFatFilename(filename);
        
        return filename;
    }
    
    /*
     * Parse the Content-Disposition HTTP Header. The format of the header is defined here:
     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
     * content that is going to be downloaded to the file system. We only support the attachment
     * type.
     */
    private static String parseContentDisposition(String contentDisposition) {
        try {
            Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
            if (m.find()) {
                return m.group(1);
            }
        } catch (IllegalStateException ex) {
            // This function is defined as returning null when it can't parse the header
        }
        return null;
    }
    
    /**
     * Mutate the given filename to make it valid for a FAT filesystem, replacing any invalid
     * characters with "_".
     */
    public static String buildValidFatFilename(String name) {
        if (TextUtils.isEmpty(name) || ONE_DOT.equals(name) || TWO_DOT.equals(name)) {
            return "(invalid)";
        }
        final StringBuilder res = new StringBuilder(name.length());
        for (int i = 0; i < name.length(); i++) {
            final char c = name.charAt(i);
            if (isValidFatFilenameChar(c)) {
                res.append(c);
            } else {
                res.append('_');
            }
        }
        return res.toString();
    }
    
    private static boolean isValidFatFilenameChar(char c) {
        if ((0x00 <= c && c <= 0x1f)) {
            return false;
        }
        switch (c) {
        case '"':
        case '*':
        case '/':
        case ':':
        case '<':
        case '>':
        case '?':
        case '\\':
        case '|':
        case 0x7F:
            return false;
        default:
            return true;
        }
    }
    
    private static String chooseExtensionFromMimeType(String mimeType, boolean useDefaults) {
        String extension = null;
        if (mimeType != null) {
            extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
            if (extension != null) {
                VLog.v(TAG, "adding extension from type");
                extension = "." + extension;
            } else {
                VLog.v(TAG, "couldn't find extension for " + mimeType);
            }
        }
        if (extension == null) {
            if (mimeType != null && mimeType.toLowerCase().startsWith("text/")) {
                if (mimeType.equalsIgnoreCase("text/html")) {
                    VLog.v(TAG, "adding default html extension");
                    extension = Constants.DEFAULT_DL_HTML_EXTENSION;
                } else if (useDefaults) {
                    VLog.v(TAG, "adding default text extension");
                    extension = Constants.DEFAULT_DL_TEXT_EXTENSION;
                }
            } else if (useDefaults) {
                VLog.v(TAG, "adding default binary extension");
                extension = Constants.DEFAULT_DL_BINARY_EXTENSION;
            }
        }
        return extension;
    }
    
    private static String chooseExtensionFromFilename(String mimeType, String filename, int lastDotIndex) {
        String extension = null;
        if (mimeType != null) {
            // Compare the last segment of the extension against the mime type.
            // If there's a mismatch, discard the entire extension.
            String typeFromExt = MimeTypeMap.getSingleton()
                    .getMimeTypeFromExtension(filename.substring(lastDotIndex + 1));
            if (typeFromExt == null || !typeFromExt.equalsIgnoreCase(mimeType)) {
                extension = chooseExtensionFromMimeType(mimeType, false);
                if (extension != null) {
                    VLog.v(TAG, "substituting extension from type");
                } else {
                    VLog.v(TAG, "couldn't find extension for " + mimeType);
                }
            }
        }
        if (extension == null) {
            VLog.v(TAG, "keeping extension");
            extension = filename.substring(lastDotIndex);
        }
        return extension;
    }
    
    private static String generateAvailableFilenameLocked(File parent, String prefix, String suffix)
            throws IOException {
        String name = prefix + suffix;
        if (new File(parent, name).exists()) {
            
        } else {
            return name;
        }
        
        /*
         * This number is used to generate partially randomized filenames to avoid collisions. It
         * starts at 1. The next 9 iterations increment it by 1 at a time (up to 10). The next 9
         * iterations increment it by 1 to 10 (random) at a time. The next 9 iterations increment it
         * by 1 to 100 (random) at a time. ... Up to the point where it increases by 100000000 at a
         * time. (the maximum value that can be reached is 1000000000) As soon as a number is
         * reached that generates a filename that doesn't exist, that filename is used. If the
         * filename coming in is [base].[ext], the generated filenames are [base]-[sequence].[ext].
         */
        int sequence = 1;
        for (int magnitude = 1; magnitude < 1000000000; magnitude *= 10) {
            for (int iteration = 0; iteration < 9; ++iteration) {
                name = prefix + Constants.FILENAME_SEQUENCE_SEPARATOR + sequence + suffix;
                if (new File(parent, name).exists()) {
                    sequence += sRandom.nextInt(magnitude) + 1;
                } else {
                    return name;
                }
            }
        }
        
        throw new IOException("Failed to generate an available filename");
    }
    
    // ArrayList转数组
    public static Long[] list2Array(ArrayList<Long> list) {
        Long[] array = new Long[list.size()];
        for (int i = 0; i < list.size(); i++) {
            array[i] = list.get(i);
        }
        return array;
    }
    
}
